﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Bit.Art.Core;
using Bit.Art.Core.Extensions;
using Bit.Art.Core.Models;
using Bit.Art.Sso.Db;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NetMQ;
using NetMQ.Sockets;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
using IdentityModel;
using System.Text;
using System.Linq;

namespace Bit.Art.Sso.Api
{
    public class Program
    {
        private static ILogger _logger;
        private static volatile bool s_Runing = true;
        internal static IConfigurationRoot Configuration { get; set; }
        public static IServiceProvider GlobalServiceProvider { get; set; }

#if UNITTEST
        public static System.Threading.AutoResetEvent s_autoResetEventForTest { get; private set; } = new System.Threading.AutoResetEvent(false);
#endif

        static void Main(string[] args)
        {
            Console.WriteLine("=========== SSO API ============");
            InitializeConfiguration();
            RegisterServices();
            var tasks = StartEngine();
            Task.WaitAll(tasks);
        }

        public static void RegisterServices()
        {
            var services = new ServiceCollection();
            var connectionString = Configuration.GetConnectionString("DefaultConnection");
            var migrationsAssembly = typeof(Program).GetTypeInfo().Assembly.GetName().Name;
            services.AddDbContext<ApplicationUserDbContext>(options =>
                options.UseMySql(connectionString, sql => sql.MigrationsAssembly(migrationsAssembly)));

            services.AddIdentity<ApplicationUser, IdentityRole<ulong>>()
                    .AddEntityFrameworkStores<ApplicationUserDbContext>();

            GlobalServiceProvider = services
                .AddLogging()
                .BuildServiceProvider();   
        }

        public static void InitializeConfiguration()
        {
            var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
            var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory())
                                                    .AddJsonFile("appsettings.json");

            if (!String.IsNullOrEmpty(environment))
                builder = builder.AddJsonFile($"appsettings.{environment}.json");
            
            Configuration = builder.Build();
            ApplicationLogging.LoggerFactory.AddConsole(Configuration.GetSection("Logging"));
            ApplicationLogging.LoggerFactory.AddFile("Logs/ssoapi-{Date}.log");
        }

        public static Task[] StartEngine()
        {
            var tasks = new List<Task>();

            _logger = ApplicationLogging.LoggerFactory.CreateLogger<Program>();
            tasks.Add(Task.Run(async () =>
            {
                try
                {
                    using (var response = new ResponseSocket(string.Format("@{0}", Constants.SsoApiServerEndpoint)))
                    {
#if UNITTEST
                        s_autoResetEventForTest.Set();
#endif
                        while (s_Runing)
                        {
                            try
                            {
                                var mmsg = response.ReceiveMultipartMessage();
                                var json = mmsg.Last.ConvertToString(System.Text.Encoding.UTF8);
                                var amsg = Utility.From<AccountMessage>(json);
                                string result = null;
                                switch (amsg.Type)
                                {
                                    case (int)AccountMessageType.Register:
                                        result = await DoRegister(json, amsg);
                                        break;

                                    case (int)AccountMessageType.UpdateProfile:
                                        result = await DoUpdateProfile(json, amsg);
                                        break;

                                    default:
                                        break;
                                }

                                response.SendFrame(result);
#if UNITTEST
                                s_autoResetEventForTest.Set();
#endif
                            }
                            catch (Exception e)
                            {
                                // response.SendFrame(Constants.FAIL_MSG);
                                var callret = new ApiCallResult<ulong>();
                                callret.Success = false;
                                callret.Message = e.Message;
                                callret.Result = Constants.ErrorCodes.SERVER_ERROR;
                                response.SendFrame(callret.ToJson());
#if UNITTEST
                                s_autoResetEventForTest.Set();
#endif
                                _logger.LogError(new EventId(4), e, "Sso Api节点在处理消息时出错！");
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    _logger.LogError(new EventId(3), e, "Sso Api节点在处理消息时出错！");
#if UNITTEST
                    s_autoResetEventForTest.Set();
#endif
                }
            }));

            return tasks.ToArray();
        }

        private static async Task<string> DoUpdateProfile(string json, AccountMessage amsg)
        {
            var callret = new ApiCallResult<ulong>();
            var userManager = GlobalServiceProvider.GetService<UserManager<ApplicationUser>>();
            var user = await userManager.FindByNameAsync(amsg.Username);
            var claims = new List<Claim>();
            if (!string.IsNullOrEmpty(amsg.Cellphone))
                claims.Add(new Claim(JwtClaimTypes.PhoneNumber, amsg.Cellphone));
            if (!string.IsNullOrEmpty(amsg.Nickname))
                claims.Add(new Claim(JwtClaimTypes.NickName, amsg.Nickname));
            if (!string.IsNullOrEmpty(amsg.Avatar))
                claims.Add(new Claim(JwtClaimTypes.Picture, amsg.Avatar));

            var existingClaims = await userManager.GetClaimsAsync(user);
            existingClaims = existingClaims.Where(c => claims.Any(ci => string.Compare(c.Type, ci.Type) == 0)).ToList();
            var result = await userManager.RemoveClaimsAsync(user, existingClaims);
            if (result.Succeeded)
            {
                result = await userManager.AddClaimsAsync(user, claims.ToArray());
                if (!result.Succeeded)
                {
                    callret.Message = GetErrorMessage(result.Errors);
                    _logger.LogCritical($"[DoUpdateProfile] 增加用户信息失败，来源请求：{json}，错误消息：{callret.Message}");
                }
                else
                {
                    callret.Success = true;
                    callret.Result = 0;
                }
            }
            else
            {
                callret.ErrorCode = Constants.ErrorCodes.UPDATE_USER_INFO_ERROR;
                var sb = new StringBuilder();
                foreach (var error in result.Errors)
                {
                    sb.AppendLine(error.Description);
                }
                callret.Message = sb.ToString();
                callret.Result = Constants.ErrorCodes.UPDATE_USER_INFO_ERROR;
                _logger.LogCritical($"[DoUpdateProfile] 删除用户信息失败，来源请求：{json}，错误消息：{callret.Message}");
            }

            return callret.ToJson();
        }

        private static async Task<string> DoRegister(string json, AccountMessage amsg)
        {
            var userManager = GlobalServiceProvider.GetService<UserManager<ApplicationUser>>();
            var callret = new ApiCallResult<ulong>();
            var user = new ApplicationUser()
            {
                UserName = amsg.Username,
                PhoneNumber = amsg.Cellphone
            };

            var existing = await userManager.FindByNameAsync(amsg.Username);
            if (existing != null)
            {
                callret.Result = 0;
                callret.Success = false;
                callret.Message = string.Format($"用户名{amsg.Username}已经存在了！");
                return callret.ToJson();
            }

            IList<ApplicationUser> users = null;
            if (!string.IsNullOrWhiteSpace(amsg.Cellphone))
            {
                users = await userManager.GetUsersForClaimAsync(new Claim(JwtClaimTypes.PhoneNumber, amsg.Cellphone));
                if (users != null && users.Count > 0)
                {
                    callret.Result = 0;
                    callret.Success = false;
                    callret.Message = string.Format($"用户名{amsg.Cellphone}已经存在了！");
                    return callret.ToJson();
                }
            }

            if (!string.IsNullOrWhiteSpace(amsg.Nickname))
            {
                users = await userManager.GetUsersForClaimAsync(new Claim(JwtClaimTypes.NickName, amsg.Nickname));
                if (users != null && users.Count > 0)
                {
                    callret.Result = 0;
                    callret.Success = false;
                    callret.Message = string.Format($"用户名{amsg.Nickname}已经存在了！");
                    return callret.ToJson();
                }
            }

            var result = await userManager.CreateAsync(user, amsg.Password);
            ulong ret = 0;
            if (result.Succeeded)
            {
                var uidstr = await userManager.GetUserIdAsync(user);
                var claims = new List<Claim>();

                if (!string.IsNullOrEmpty(amsg.Cellphone))
                    claims.Add(new Claim(JwtClaimTypes.PhoneNumber, amsg.Cellphone));
                if (!string.IsNullOrEmpty(amsg.Nickname))
                    claims.Add(new Claim(JwtClaimTypes.NickName, amsg.Nickname));
                if (!string.IsNullOrEmpty(amsg.Avatar))
                    claims.Add(new Claim(JwtClaimTypes.Picture, amsg.Avatar));

                if (claims.Count > 0)
                {
                    result = await userManager.AddClaimsAsync(user, claims.ToArray());
                    if (!result.Succeeded)
                    { 
                        callret.Message = GetErrorMessage(result.Errors);
                        _logger.LogCritical($"[DoRegister] 更新用户信息失败，来源请求：{json}，错误消息：{callret.Message}");
                    }
                }

                callret.Success = ulong.TryParse(uidstr, out ret);
                callret.Result = ret;
            }
            else
            {
                callret.ErrorCode = Constants.ErrorCodes.CREARE_NEW_USER_FAILED;
                callret.Message = GetErrorMessage(result.Errors);
                _logger.LogCritical($"[DoRegister] 失败，来源请求：{json}，错误消息：{callret.Message}");
            }

            return callret.ToJson();
        }

        private static string GetErrorMessage(IEnumerable<IdentityError> errors)
        {
            var sb = new StringBuilder();
            foreach (var error in errors)
            {
                sb.AppendLine(error.Description);
            }
            return sb.ToString();
        }
    }
}
